﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using RPGOnlineWPF.Renderer;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D;
using SharpDX.Direct3D10;
using SharpDX.DXGI;
using Buffer = SharpDX.Direct3D10.Buffer;
using Device = SharpDX.Direct3D10.Device;

namespace RPGOnlineWPF.Model.MapEditor.Designers
{
	public class WallDesigner : BaseInteractive<D3D10>
	{
		private readonly Wall m_designedWall;
		private D3D10 m_renderer;
		private Effect m_simpleEffect;
		private Buffer m_vertices;
		private InputLayout m_vertexLayout;
		private List<Vector4> m_wallVertices = new List<Vector4>();
		private int m_movingPoint;
		private bool m_rebuildBuffers = true;

		public WallDesigner(Wall designedWall)
		{
			m_designedWall = designedWall;
		}

		public override D3D10 Renderer
		{
			get { return m_renderer; }
			set { m_renderer = value; }
		}

		public override void Attach()
		{
			Device device = m_renderer.Device;
			if (device == null)
				return;

			var compilationResult = ShaderBytecode.CompileFromFile("Resources\\PolylineDesigner.fx", "fx_4_0");
			if (compilationResult.HasErrors)
				MessageBox.Show(compilationResult.Message);

			ShaderBytecode shaderBytes = compilationResult;
			m_simpleEffect = new Effect(device, shaderBytes);

			EffectTechnique technique = m_simpleEffect.GetTechniqueByIndex(0);
			EffectPass pass = technique.GetPassByIndex(0);

			m_vertexLayout = new InputLayout(device, pass.Description.Signature, new[] {
                new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0)
            });
		}

		private void BuildBuffers()
		{
			Device device = m_renderer.Device;
			if (device == null || !m_rebuildBuffers)
				return;

			if (m_wallVertices.Count != m_designedWall.PointsCount)
			{
				if(m_vertices != null) m_vertices.Dispose();

				m_vertices = new Buffer(device, new BufferDescription()
				{
					BindFlags = BindFlags.VertexBuffer,
					CpuAccessFlags = CpuAccessFlags.Write,
					OptionFlags = ResourceOptionFlags.None,
					SizeInBytes = (m_designedWall.PointsCount + 1) * Marshal.SizeOf(typeof(Vector4)),
					Usage = ResourceUsage.Dynamic
				});
			}

			m_wallVertices = m_designedWall.Points.ToList();

			using (var vertexStream = m_vertices.Map(MapMode.WriteDiscard))
			{
				var allVerticesPlusFirst = new Vector4[m_wallVertices.Count + 1];
				m_wallVertices.CopyTo(allVerticesPlusFirst);
				allVerticesPlusFirst[m_wallVertices.Count] = m_wallVertices[0];

				vertexStream.WriteRange(allVerticesPlusFirst);
				vertexStream.Position = 0;
				m_vertices.Unmap();
			}

			m_rebuildBuffers = false;
		}

		public override void Detach()
		{
			m_simpleEffect.Dispose();
			m_vertices.Dispose();
		}

		public override void RenderScene(DrawEventArgs args)
		{
			Device device = m_renderer.Device;
			if (device == null || !m_designedWall.Selected)
				return;

			BuildBuffers();

			device.InputAssembler.InputLayout = m_vertexLayout;
			device.InputAssembler.PrimitiveTopology = PrimitiveTopology.LineStrip;
			device.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(m_vertices, Marshal.SizeOf(typeof(Vector4)), 0));

			var color = m_simpleEffect.GetVariableBySemantic("Color").AsVector();
			color.Set(Color4.White);

			var modelViewProjection = m_renderer.Camera.View * m_renderer.Camera.Projection;
			var matrix = m_simpleEffect.GetVariableBySemantic("ModelViewProjection").AsMatrix();
			matrix.SetMatrix(modelViewProjection);

			var viewport = m_simpleEffect.GetVariableBySemantic("Viewport").AsVector();
			var viewports = m_renderer.Device.Rasterizer.GetViewports();
			viewport.Set(new Vector2(viewports[0].Width, viewports[0].Height));

			EffectTechnique technique = m_simpleEffect.GetTechniqueByIndex(0);
			for (int i = 0; i < technique.Description.PassCount; ++i)
			{
				EffectPass pass = technique.GetPassByIndex(i);
				pass.Apply();
				device.Draw(m_wallVertices.Count+1, 0);
			}

			device.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
			technique = m_simpleEffect.GetTechniqueByIndex(1);
			for (int i = 0; i < technique.Description.PassCount; ++i)
			{
				EffectPass pass = technique.GetPassByIndex(i);
				pass.Apply();
				device.Draw(m_wallVertices.Count+1, 0);
			}
		}

		public override void Reset(DrawEventArgs args)
		{
		}

		public override void Render(DrawEventArgs args)
		{
		}

		public override void OnMouseDown(UIElement ui, MouseButtonEventArgs e)
		{
			if (m_designedWall.Selected)
			{
				if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Pressed)
				{
					m_movingPoint = GetPointUnderMouse(ui, e);
					e.Handled = m_movingPoint != -1;
				}
			}
			base.OnMouseDown(ui, e);
		}

		public override void OnMouseMove(UIElement ui, MouseEventArgs e)
		{
			if (m_designedWall.Selected)
			{
				if (e.LeftButton == MouseButtonState.Pressed && m_movingPoint != -1)
				{
					var worldMousePosition = GetWorldMousePosition(ui, e);
					m_designedWall.ChangePoint(m_movingPoint, worldMousePosition);
					m_rebuildBuffers = true;

					e.Handled = true;
				}
				else
				{
					var screenMousePosition = GetScreenMousePosition(ui, e);
					var uiControl = (FrameworkElement) ui;
					uiControl.Cursor = Cursors.Arrow;

					int pointUnderMouse = GetPointUnderMouse(screenMousePosition);
					if (pointUnderMouse != -1)
						uiControl.Cursor = Cursors.SizeAll;
				}
			}

			base.OnMouseMove(ui, e);
		}

		public override void OnMouseUp(UIElement ui, MouseButtonEventArgs e)
		{
			if (m_designedWall.Selected)
			{
				if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Released && m_movingPoint != -1)
				{
					m_movingPoint = -1;
					e.Handled = true;
				}
			}

			base.OnMouseUp(ui, e);
		}

		private Vector2 GetScreenMousePosition(UIElement ui, MouseEventArgs e)
		{
			var position = e.GetPosition(ui);
			return new Vector2((float)position.X, (float)position.Y);
		}

		private Vector4 GetWorldMousePosition(UIElement ui, MouseEventArgs e)
		{
			var mousePosition = m_renderer.Camera.GetVector(ui, e);

			float snapX = (float)(Math.Round(mousePosition.X / 1.5f) * 1.5f);
			float snapY = (float)(Math.Round(mousePosition.Y / 1.5f) * 1.5f);

			return new Vector4(snapX, snapY, 0, 1);
		}

		private int GetPointUnderMouse(UIElement ui, MouseEventArgs e)
		{
			return GetPointUnderMouse(GetScreenMousePosition(ui, e));
		}

		private int GetPointUnderMouse(Vector2 screenMousePosition)
		{
			int pointIndex = 0;
			foreach (var wallVertex in m_wallVertices)
			{
				Vector2 screenPosition = m_renderer.Camera.Project(wallVertex);
				var distanceFromPoint = (screenMousePosition - screenPosition).Length();
				if (distanceFromPoint < 10)
					return pointIndex;

				pointIndex++;
			}

			return -1;
		}
	}
}